"""Shopify GraphQL client helpers."""

from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Set

import frappe

try:  # pragma: no cover - runtime dependency
    import requests
except ModuleNotFoundError:  # pragma: no cover
    requests = None  # type: ignore


class ShopifyClientError(Exception):
    """Raised when Shopify API returns an error."""


@dataclass
class ShopifyShopConfig:
    name: str
    shopurl: str
    shoptoken: str


class ShopifyClient:
    """Lightweight GraphQL client tailored for Shopify Admin API."""

    def __init__(
        self,
        shop_name: str,
        user: Optional[str] = None,
        company: Optional[str] = None,
    ):
        self.shop_name = shop_name
        self.user = user or getattr(frappe.session, "user", None) or "Administrator"
        self.company = company
        self.config = self._load_shop_config()

    def _load_shop_config(self) -> ShopifyShopConfig:
        # 严格按照shop_name查找usershop记录
        shop = frappe.get_value(
            "usershop",
            {"name": self.shop_name},
            ["name", "shopurl", "shoptoken"],
            as_dict=True,
        )
        
        if not shop:
            raise ShopifyClientError(f"未找到Shopify店铺配置: {self.shop_name}")

        if not shop.shopurl or not shop.shoptoken:
            raise ShopifyClientError("Shopify店铺缺少 shopurl 或 shoptoken 配置")

        shop_url = (shop.shopurl or "").strip()
        if shop_url.endswith("graphql.json"):
            shop_url = shop_url
        else:
            shop_url = shop_url.rstrip("/")

        return ShopifyShopConfig(
            name=shop.name,
            shopurl=shop_url,
            shoptoken=shop.shoptoken,
        )

    # ------------------------------------------------------------------
    #  GraphQL调用
    # ------------------------------------------------------------------

    def _post_graphql(self, query: str, variables: Optional[Dict] = None) -> Dict:
        if requests is None:  # pragma: no cover
            raise ShopifyClientError("requests 模块未安装")

        payload = {"query": query, "variables": variables or {}}

        headers = {
            "Content-Type": "application/json",
            "X-Shopify-Access-Token": self.config.shoptoken,
        }


        response = requests.post(
            self.config.shopurl,
            headers=headers,
            json=payload,
            timeout=60,
        )


        try:
            response.raise_for_status()
        except Exception as exc:  # pragma: no cover - HTTP errors forwarded
            raise ShopifyClientError(
                f"Shopify HTTP请求失败: {exc} ({getattr(response, 'status_code', 'n/a')})"
            ) from exc

        data = response.json()

        if data.get("errors"):
            from json import dumps
            error_msg = dumps(data["errors"], ensure_ascii=False)
            raise ShopifyClientError(error_msg)

        return data.get("data", {})

    # ------------------------------------------------------------------
    #  业务查询
    # ------------------------------------------------------------------

    def fetch_orders(
        self,
        first: int = 50,
        after: Optional[str] = None,
        query: Optional[str] = None,
    ) -> Tuple[List[Dict], Optional[str], bool]:
        """获取订单列表，返回 (orders, end_cursor, has_next_page)."""

        gql = """
        query ($first: Int!, $after: String, $query: String) {
          orders(first: $first, after: $after, sortKey: CREATED_AT, reverse: true, query: $query) {
            pageInfo {
              hasNextPage
              endCursor
            }
            edges {
              cursor
              node {
                id
                name
                orderNumber: legacyResourceId
                confirmed
                createdAt
                updatedAt
                phone
                subtotalPrice
                totalPrice
                displayFinancialStatus
                displayFulfillmentStatus
                currencyCode
                presentmentCurrencyCode
                tags
                note
                shippingAddress {
                  address1
                  address2
                  city
                  company
                  coordinatesValidated
                  country
                  countryCode
                  countryCodeV2
                  firstName
                  formatted
                  formattedArea
                  id
                  lastName
                  latitude
                  longitude
                  name
                  phone
                  province
                  provinceCode
                  timeZone
                  validationResultSummary
                  zip
                }
                billingAddress {
                  address1
                  address2
                  city
                  company
                  coordinatesValidated
                  country
                  countryCode
                  countryCodeV2
                  firstName
                  formatted
                  formattedArea
                  id
                  lastName
                  latitude
                  longitude
                  name
                  phone
                  province
                  provinceCode
                  timeZone
                  validationResultSummary
                  zip
                }
                customer {
                  canDelete
                  createdAt
                  dataSaleOptOut
                  displayName
                  email
                  firstName
                  hasTimelineComment
                  id
                  lastName
                  legacyResourceId
                  lifetimeDuration
                  locale
                  multipassIdentifier
                  note
                  numberOfOrders
                  phone
                  productSubscriberStatus
                  state
                  tags
                  taxExempt
                  taxExemptions
                  unsubscribeUrl
                  updatedAt
                  validEmailAddress
                  verifiedEmail
                  addresses {
                    address1
                    address2
                    city
                    company
                    coordinatesValidated
                    country
                    countryCode
                    countryCodeV2
                    firstName
                    formatted
                    formattedArea
                    id
                    lastName
                    latitude
                    longitude
                    name
                    phone
                    province
                    provinceCode
                    timeZone
                    validationResultSummary
                    zip
                  }
                }
                lineItems(first: 100) {
                  nodes {
                    canRestock
                    currentQuantity
                    discountedTotal
                    discountedUnitPrice
                    fulfillableQuantity
                    fulfillmentStatus
                    id
                    isGiftCard
                    merchantEditable
                    name
                    nonFulfillableQuantity
                    originalTotal
                    originalUnitPrice
                    quantity
                    refundableQuantity
                    requiresShipping
                    restockable
                    sku
                    taxable
                    title
                    totalDiscount
                    unfulfilledDiscountedTotal
                    unfulfilledOriginalTotal
                    unfulfilledQuantity
                    variantTitle
                    vendor
                  }
                }
                fulfillmentOrders(first: 10) {
                  edges {
                    node {
                      id
                      status
                      assignedLocation { name }
                      lineItems(first: 100) {
                        edges {
                          node {
                            id
                            lineItem { id }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
        """

        variables = {"first": first, "after": after, "query": query}
        data = self._post_graphql(gql, variables)

        orders = [edge["node"] for edge in data.get("orders", {}).get("edges", [])]
        page_info = data.get("orders", {}).get("pageInfo", {})

        return orders, page_info.get("endCursor"), page_info.get("hasNextPage", False)

    def fetch_skus(
        self,
        first: int = 50,
        after: Optional[str] = None,
        query: Optional[str] = None,
    ) -> Tuple[List[Dict], Optional[str], bool]:
        """获取SKU（产品变体）列表。"""

        gql = """
        query ($first: Int!, $after: String, $query: String) {
          products(first: $first, after: $after, query: $query, sortKey: UPDATED_AT) {
            edges {
              cursor
              node {
                id
                title
                status
                updatedAt
                productType
                vendor
                tags
                description
                descriptionHtml
                totalInventory
                tracksInventory
                images(first: 100) {
                  edges {
                    node {
                      id
                      altText
                      originalSrc
                      src
                      width
                      height
                    }
                  }
                }
                featuredImage {
                  id
                  altText
                  originalSrc
                  src
                  width
                  height
                }
                priceRangeV2 {
                  minVariantPrice {
                    amount
                    currencyCode
                  }
                  maxVariantPrice {
                    amount
                    currencyCode
                  }
                }
                category {
                  id
                  name
                  fullName
                }
                variants(first: 100) {
                  edges {
                    node {
                      id
                      title
                      sku
                      price
                      compareAtPrice
                      availableForSale
                      inventoryQuantity
                      sellableOnlineQuantity
                      barcode
                      taxable
                      taxCode
                      updatedAt
                    }
                  }
                }
              }
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
        """

        variables = {"first": first, "after": after, "query": query}
        data = self._post_graphql(gql, variables)

        products = [edge for edge in data.get("products", {}).get("edges", [])]
        page_info = data.get("products", {}).get("pageInfo", {})

        sku_entries: List[Dict] = []
        for product_edge in products:
            product_node = product_edge.get("node", {})
            variants = product_node.get("variants", {}).get("edges", [])
            
            for variant_edge in variants:
                variant_node = variant_edge.get("node", {})
                sku_entries.append({
                    "product": product_node,
                    "variant": variant_node,
                })

        return sku_entries, page_info.get("endCursor"), page_info.get("hasNextPage", False)


    # ------------------------------------------------------------------
    #  库存查询：按 SKU + Location 名称维度返回 on_hand 数量
    # ------------------------------------------------------------------

    def fetch_inventory_by_skus(
        self,
        skus: List[str],
        location_ids: Optional[List[str]] = None,
    ) -> Dict[str, Dict]:
        """
        根据一批 SKU（以及可选的 Location）返回 Shopify 库存（on_hand）。

        Args:
            skus: SKU 列表（前端多选列表传入即可，数量建议不要太大）
            location_ids: 可选的 Location ID 列表（可以是纯数字 ID 或完整 GID）。

        Returns:
            形如：

            {
              "SKU_001": {
                "sku": "SKU_001",
                "total_on_hand": 10,
                "variants": [
                  {
                    "variant_id": "...",
                    "inventory_item_id": "...",
                    "total_on_hand": 10,
                    "locations": [
                      {
                        "location_id": "gid://shopify/Location/7609...",
                        "location_name": "Shop location",
                        "on_hand": 10
                      }
                    ]
                  }
                ]
              },
              ...
            }
        """

        cleaned_skus: List[str] = [
            s.strip() for s in (skus or []) if isinstance(s, str) and s.strip()
        ]
        if not cleaned_skus:
            return {}

        # 允许的 Location 集合（按名称，小写），为空则表示不过滤
        allowed_locations: Set[str] = set()
        if location_ids:
            for loc in location_ids:
                if not loc:
                    continue
                allowed_locations.add(str(loc).strip().lower())

        # GraphQL 查询模板（single batch，first 上限 250）
        gql = """
        query ($first: Int!, $query: String!) {
          productVariants(first: $first, query: $query) {
            edges {
              node {
                id
                sku
                inventoryItem {
                  id
                  inventoryLevels(first: 50) {
                    edges {
                      node {
                        id
                        quantities(names: ["on_hand"]) {
                          name
                          quantity
                        }
                        location {
                          id
                          name
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
        """

        result: Dict[str, Dict] = {}

        # 拆批，每批最多 250 个 SKU
        batch_size = 250
        for i in range(0, len(cleaned_skus), batch_size):
            batch = cleaned_skus[i : i + batch_size]
            search_query = " OR ".join(f"sku:{sku}" for sku in batch)

            variables = {"first": batch_size, "query": search_query}
            data = self._post_graphql(gql, variables)
            edges = data.get("productVariants", {}).get("edges", []) or []

            for edge in edges:
                node = edge.get("node") or {}
                sku = (node.get("sku") or "").strip()
                if not sku:
                    continue

                inv_item = node.get("inventoryItem") or {}
                levels = (inv_item.get("inventoryLevels") or {}).get("edges", []) or []

                variant_locations: List[Dict] = []
                variant_total_on_hand = 0

                for lvl_edge in levels:
                    lvl_node = lvl_edge.get("node") or {}
                    loc = (lvl_node.get("location") or {}) or {}
                    loc_id = loc.get("id")
                    loc_name = loc.get("name")
                    loc_name_key = (loc_name or "").strip().lower()

                    if allowed_locations and loc_name_key not in allowed_locations:
                        continue

                    quantities = lvl_node.get("quantities") or []
                    on_hand = 0
                    for q in quantities:
                        if q.get("name") == "on_hand":
                            try:
                                on_hand = int(q.get("quantity") or 0)
                            except Exception:
                                on_hand = 0
                            break

                    variant_total_on_hand += on_hand
                    variant_locations.append(
                        {
                            "inventory_level_id": lvl_node.get("id"),
                            "location_id": loc_id,
                            "location_name": loc_name,
                            "on_hand": on_hand,
                        }
                    )

                variant_info = {
                    "variant_id": node.get("id"),
                    "inventory_item_id": inv_item.get("id") if inv_item else None,
                    "total_on_hand": variant_total_on_hand,
                    "locations": variant_locations,
                }

                sku_entry = result.setdefault(
                    sku,
                    {
                        "sku": sku,
                        "total_on_hand": 0,
                        "variants": [],
                    },
                )
                sku_entry["variants"].append(variant_info)
                sku_entry["total_on_hand"] += variant_total_on_hand

        return result

